"""
Created on 15 Dec 2020
@author:JemusD
"""
from matplotlib import pyplot as plt
from matplotlib import lines as mlines
from matplotlib.font_manager import FontProperties
import matplotlib
import numpy as np
# sorted 里面给字典选择key还是value排序用到的一个包
import operator
import time

# Create the data set
def createDataSet():
    group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group, labels


"""
instructions: KNN算法，分类器
    主要作用：
            1.实现该功能 sqrt((x1-x2)^2 + (y1-y2)^2) # x1,y1表示特征
            2.给前k个标签计数，看哪个标签数量最多，并返回这个标签
Parameters: 
    inX - 要预测的数据<测试集>
    dataSet - 训练集
    label - 分类标准<right answer>
    k - KNN算法参数，选择k个距离最小的点
Returns:
    sortedClassCount - 分类结果(也可以理解为：将字典中第一行的key-value中的value返回)
Modify:
    2020-12-15
"""
# classify function: 
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    # 函数tile(array, y) y表示array重复方式,y=(1,5) 表示一行5个array. y=(5,1) 表示5行每行一个array.
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 注意要用np.array()生成的矩阵才可以用 x**2，及每个元素平方
    sqDiffMat = diffMat**2
    # sum(0) col列相加，sum(1) row行相加
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    # ^以上即为 sqrt( (x1-x2)^2 + (y1-y2)^2 )
    # argsort() 返回数组中从小到大的索引
    sortedDistIndicies = distances.argsort()
    # print(sortedDistIndicies)

    classCount = {}
    # 一个给标签计数的循环
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        # dict.get(key, default=None) if haven't this key, return default 
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # dict.items() 返回一个可遍历的列表，里面是元组
    # key = operator.itemgetter(0) 表示按'键'进行排序
    # key = operator.itemgetter(1) 表示按'值'进行排序
    # 默认为从升序排序，reverse = True表示为降序排列
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]


"""
instructions: 打开文件，获取数据，对数据进行简单清洗，对数据进行分类， 1表示喜欢，2表示魅力一般，3表示极具魅力(类似预处理)
    将输入文件中的内容第前3列读取出来，并按照这个顺序将第4列（标签列）赋值给classLabelVector
Parameters: 
    filename - 文件名
Returns:
    returnMat - 特征矩阵（属性矩阵）
    classLabelVector - 分类label向量
Modify:
    2020-12-15
"""
# 将文本记录转换成Numpy的解析程序
def file2matrix(filename):
    fr = open(filename)
    # readlines() 这个函数有s，是读取文件所有行数据
    arrayOfLines = fr.readlines()
    numberOfLines = len(arrayOfLines)
    # 要返回的矩阵 matrix. zeros函数，生成一个（n,3）的0矩阵
    returnMat = np.zeros((numberOfLines, 3))
    classLabelVector = []
    index = 0

    for line in arrayOfLines:
        line = line.strip()
        # split('\t'),以tab为分割点，将数据分割成list
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]

        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)

        index += 1
    return returnMat, classLabelVector


"""
instruction: visualization
    主要功能：
        1.实现数据可视化
Parameters:
    datingDataMat - 特征矩阵
    datingLabels - 分类label
Returns:
    None
Modify:
    2020-12-15
"""
# visualization
def showDatas(datingDataMat, datingLabels):
    font = FontProperties(fname=r"./msyh.ttf", size=14)
    # 当nrows=2,ncols=2，表示fig画布被分为4个区域，axs[0][0]代表第一行第一个区域
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13,8))
    LabelsColors = []
    
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')
    
    # 画出散点图，以datingDataMat矩阵第一列为x，第二列为y，散点大小为15, 透明度为0.5
    axs[0][0].scatter(x=datingDataMat[:,0], y=datingDataMat[:,1], color=LabelsColors, s=15, alpha=0.5)
    # 设置标题，x轴label， y轴label
    axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
    
    # 画出散点图，以datingDataMat矩阵第一列为x，第三列为y，散点大小为15, 透明度为0.5
    axs[0][1].scatter(x=datingDataMat[:,0], y=datingDataMat[:,2], color=LabelsColors, s=15, alpha=0.5)
    # 设置标题，x轴label， y轴label
    axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰淇淋公升数', FontProperties=font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font)
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
    
    # 画出散点图，以datingDataMat矩阵第二列为x，第三列为y，散点大小为15, 透明度为0.5
    axs[1][0].scatter(x=datingDataMat[:,1], y=datingDataMat[:,2], color=LabelsColors, s=15, alpha=0.5)
    # 设置标题，x轴label， y轴label
    axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰淇淋公升数', FontProperties=font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰淇淋公升数', FontProperties=font)
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
    # 设置图例
    didntLike = mlines.Line2D([], [], color='black', marker='.', markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.', markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.', markersize=6, label='largeDoses')
    
    # 添加图例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
    
    # 显示图片
    plt.show()


"""
instruction: normalization for the data set
Parameters:
    dataSet - 特征矩阵<要归一化的数据集>
Returns:
    normaDataSet - 归一化的数据集
    ranges - 数据的范围
    minVals - 数据的最小值
Modify:
    2020-12-16
"""
# normalization
def autoNorm(dataSet):
    # get the min_value of the cols
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    ranges = maxVals - minVals
    normDataSet = np.zeros(np.shape(dataSet))
    m = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (m,1))
    normDataSet = normDataSet / np.tile(ranges, (m,1))

    return normDataSet, ranges, minVals


"""
attention:
    注意，k邻近算法，是计算k个距离最近相邻的特征，然后看里面那种出现得最多，该要分类的点即为该出现次数多的类型
    再者，不一定是座标点[1.0,1.1] 'A'的形式，也可以是其他[4000,10,0.1] 1这种
"""
# 
def datingClassTest():
    filename = "datingTestSet.txt"
    datingDataMat, datingLabels = file2matrix(filename)
    hoRatio = 0.10
    normMat, ranges, minVals = autoNorm(datingDataMat)

    m = normMat.shape[0]
    numTestRows = int(m * hoRatio)
    errorCount = 0.0

    for i in range(numTestRows):
        classifierResult = classify0(normMat[i,:], normMat[numTestRows:m,:], datingLabels[numTestRows:m], 4)
        print("分类结果为:%d\t真实类别%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率: %f%%" % (errorCount/float(numTestRows)*100))


# ========================================================================================================================== DPD
"""
Author:
    DPD
Modify:
    2020-12-16
"""
# a little change
def testDatingDataSet():
    filename = "datingTestSet.txt"
    featureMat, Labels = file2matrix(filename)
    normData, ranges, minVals = autoNorm(featureMat)

    testRatio = 0.1
    m = normData.shape[0]
    numTestRows = int(testRatio * m)
    errorCount = 1.0

    for i in range(numTestRows):
        classifyResult = classify0(normData[i,:], normData[numTestRows:m,:], Labels[numTestRows:m], 4)
        if classifyResult != Labels[i]:
            errorCount += 1.0
        print("The classifier came back with:%d\tThe real answer is:%d" % (classifyResult, Labels[i]))
    print("error rate is:%d" % (errorCount / float(numTestRows)*100))


# input the test matrix, than predict it
def isSuitableYou(singleMat):
    resultList = ['讨厌', '喜欢', '非常喜欢']
    filename = "datingTestSet.txt"
    featureMat, Labels = file2matrix(filename)                                      # 预处理数据，提取特征矩阵
    normFeatureMat, ranges, minVals = autoNorm(featureMat)                          # 归一化处理数据
    std_singleMat = (singleMat - minVals) / ranges                                  # 对你输入的数据进行归一化
    classifierResult = classify0(std_singleMat, normFeatureMat, Labels, 4)          # 对你输入的数据进行分类
    print("the classifier came back with:%s" % (resultList[classifierResult-1]))
    showDatas(featureMat, Labels)

# ========================================================================================================================== DPD


"""

"""
def main():
    start = time.clock()
    isSuitableYou(np.array([30000,9.0,1.4]))
    end = time.clock()
    print("Running time: %f" % (end-start))

if __name__ == '__main__':
    main()